package com.example.smartwaterapi.config;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.example.smartwaterapi.common.api.ApiResponse;
import com.example.smartwaterapi.common.annotation.RateLimit;
import com.example.smartwaterapi.common.api.ErrorCode;
import com.example.smartwaterapi.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * API速率限制切面
 * 用于控制API接口的访问频率
 */
@Aspect
@Component
@Slf4j
public class RateLimitAspect {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 定义切点 - 所有使用@RateLimit注解的方法
     */
    @Pointcut("@annotation(com.example.smartwaterapi.common.annotation.RateLimit)")
    public void rateLimitPointcut() {
    }

    /**
     * 环绕通知 - 实现速率限制功能
     * 
     * @param joinPoint 连接点
     * @param rateLimit 注解信息
     * @return 方法返回结果
     * @throws Throwable 可能的异常
     */
    @Around("rateLimitPointcut() && @annotation(rateLimit)")
    public Object handleRateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getName();
        String key = rateLimit.key();
        
        // 如果没有指定key，使用类名+方法名作为key
        if (StringUtils.isEmpty(key)) {
            key = className + ":" + methodName;
        }
        
        try {
            // 获取限流参数
            int limit = rateLimit.limit();
            int period = rateLimit.period();
            TimeUnit timeUnit = rateLimit.timeUnit();
            
            // 转换为毫秒
            long periodInMillis = timeUnit.toMillis(period);
            
            // 当前时间戳
            long now = System.currentTimeMillis();
            
            // Redis中存储的数据结构: sorted set，分数为时间戳
            String redisKey = "rateLimit:" + key;
            
            // 移除过期的访问记录
            redisTemplate.opsForZSet().removeRangeByScore(redisKey, 0, now - periodInMillis);
            
            // 获取当前窗口内的请求数
            Long count = redisTemplate.opsForZSet().zCard(redisKey);
            
            // 确保count不为null，并安全转换为int
            int requestCount = (count != null) ? count.intValue() : 0;
            
            // 检查是否超过限制
            if (requestCount >= limit) {
                log.warn("API访问频率限制: {} 在 {} {} 内已达到 {} 次访问上限", key, period, timeUnit, limit);
                throw new BusinessException(ErrorCode.RATE_LIMIT_EXCEEDED);
            }
            
            // 记录本次访问
            redisTemplate.opsForZSet().add(redisKey, UUID.randomUUID().toString(), now);
            
            // 设置过期时间，避免长期占用内存
            redisTemplate.expire(redisKey, period * 2, timeUnit);
            
            // 执行原方法
            return joinPoint.proceed();
            
        } catch (BusinessException e) {
            // 业务异常直接抛出
            throw e;
        } catch (Exception e) {
            log.error("速率限制处理异常: {}", e.getMessage(), e);
            // 包装为业务异常抛出，避免暴露详细错误信息
            throw new BusinessException(ErrorCode.SYSTEM_BUSY);
        }
    }

    /**
     * 限流后的统一降级处理
     */
    public static ApiResponse<String> handleBlockException(BlockException e) {
        log.warn("接口被限流: {}", e.getRule().getResource());
        return ApiResponse.error("系统繁忙，请稍后再试");
    }
} 